/*  ----------------------------------------------------------------------------
 *  Arello Mobile
 *  Fast 3d Engine
 *  ----------------------------------------------------------------------------
 * Except where otherwise noted, this work is licensed under
 * http://creativecommons.org/licenses/by/3.0
 */

#include "DecompressorPNG.h"
#include <pictureformat/libpng/png.h>

#define PNG_SIGNATURE_SIZE 8

namespace liba
{
namespace pictureformat
{
	
struct PngData
{
	char         * data;
	unsigned int   size;
	unsigned int   offset;
	PngData(void * _data, unsigned int _size, unsigned int _offset = 0) : data((char*)_data), size(_size), offset(_offset)
	{
	}
};
	
void pngReadData(png_structp pngReader, png_bytep data, png_size_t length)
{
	// gettin data pointer
	PngData * pngData = (PngData*)png_get_io_ptr(pngReader);
	
	// compute bytes to read
	unsigned int availableBytes = pngData->size - pngData->offset;
	unsigned int bytesToRead    = availableBytes > length ? length : availableBytes;
	
	// read data
	memcpy(data, &pngData->data[pngData->offset], bytesToRead);
	pngData->offset += bytesToRead;
}
	
DecompressorPng::DecompressorPng(const void * data, int size) : _imageWidth(0), _imageHeight(0), _bitDepth(0), _channels(0), _scanLine(0), _unpackedData(NULL)
{
	// check if its PNG image
	if(png_sig_cmp((png_byte*)data, 0, PNG_SIGNATURE_SIZE) != 0)
	{
		printf("libpng error: specified image if to an PNG file.\n");
		return;
	}
	
	// create PNG format reader
	png_structp pngReader = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if(pngReader == NULL)
	{
		printf("libpng error: unable to create PNG image reader.\n");
		return;
	}
	
	// create image info object
	png_infop imageInfo = png_create_info_struct(pngReader);
	if(pngReader == NULL)
	{
		printf("libpng error: unable to create image info object.\n");
		png_destroy_read_struct(&pngReader, NULL, NULL);
		return;
	}
	
	// ointers for image rows
	png_bytep * rowPointers = NULL;
	
	// setup error handling
	if(setjmp((*png_set_longjmp_fn((pngReader), (png_longjmp_ptr)longjmp, sizeof (jmp_buf)))))
	{
		printf("libpng error: unable to read PNG file.\n");
		
		if(_unpackedData != NULL)
		{
			delete [] _unpackedData;
			_unpackedData = NULL;
		}
		
		if(rowPointers != NULL)
		{
			delete [] rowPointers;
			rowPointers = NULL;
		}
		
		png_destroy_read_struct(&pngReader, &imageInfo, NULL);
		return;
	}
	
	// sets data reading function
	PngData pngData = PngData((void*)data, size, PNG_SIGNATURE_SIZE);
	png_set_read_fn(pngReader, &pngData, pngReadData);
	
	// we already read PNG_SIGNATURE_SIZE bytes for PNG signature
	png_set_sig_bytes(pngReader, PNG_SIGNATURE_SIZE);
	
    // read png info
	png_read_info(pngReader, imageInfo);
	
	// getting image info
	_imageWidth  = png_get_image_width(pngReader,  imageInfo);
	_imageHeight = png_get_image_height(pngReader, imageInfo);
	_bitDepth    = png_get_bit_depth(pngReader,    imageInfo);
	_channels    = png_get_channels(pngReader,     imageInfo);
	
	// getting image color type
	png_uint_32 colorType = png_get_color_type(pngReader, imageInfo);
	
	// converts grayscale and palettes
	switch(colorType)
	{
		case PNG_COLOR_TYPE_PALETTE:
			png_set_palette_to_rgb(pngReader);
			_channels = 3;           
			break;
			
		case PNG_COLOR_TYPE_GRAY:
			if(_bitDepth < 8)
				png_set_expand_gray_1_2_4_to_8(pngReader);
			
			_bitDepth = 8;
			break;
	}
	
	// convert alpha
	if(png_get_valid(pngReader, imageInfo, PNG_INFO_tRNS))
	{
		png_set_tRNS_to_alpha(pngReader);
		_channels += 1;
	}
	
	// convert 16 bit channels to 8 bit
	if(_bitDepth == 16)
	{
		png_set_strip_16(pngReader);
		_bitDepth = 8;
	}
	
	// update info
	png_read_update_info(pngReader, imageInfo);
	
	// allocate array for rows pointers
	rowPointers = new png_bytep[_imageHeight];
	
	// allocate unpacked data buffer
	_unpackedData = new char[_imageWidth * _imageHeight * _bitDepth * _channels / 8];
	
	// compute row stride
	const unsigned int stride = _imageWidth * _bitDepth * _channels / 8;
	
	// setup pointers to image's rows
	for(int i = 0; i < _imageHeight; i++)
	{
		unsigned int offset = (_imageHeight - i - 1) * stride;
		rowPointers[i]      = (png_bytep)_unpackedData + offset;
	}
	
	// read PNG iamge
	png_read_image(pngReader, rowPointers);
	
	// clear
	delete [] rowPointers;
	png_destroy_read_struct(&pngReader, &imageInfo, NULL);
}
	
DecompressorPng::~DecompressorPng()
{
	if(_unpackedData != NULL)
	{
		delete [] _unpackedData;
		_unpackedData = NULL;
	}
}

int DecompressorPng::get_width(int frame) const
{
	return _imageWidth;
}

int DecompressorPng::get_height(int frame) const
{
	return _imageHeight;
}

Format DecompressorPng::get_best_format() const
{
	return _channels == 4 ? pictureformat::A8R8G8B8 : pictureformat::R8G8B8;
}

int DecompressorPng::get_frame_count() const
{
	return 1;
}

void DecompressorPng::reset(Format format, int frame)
{
	_desiredFormat = format;
	_scanLine      = 0;
}

void DecompressorPng::unpack_scanline(void * destination)
{
	if(_channels == 4)
	{
		convert_scanline<R8G8B8A8>(_desiredFormat, &_unpackedData[_scanLine * get_width() * _channels * _bitDepth / 8], destination, get_width());
		//convert_scanline<A8R8G8B8>(_desiredFormat, &_unpackedData[_scanLine * get_width() * _channels * _bitDepth / 8], destination, get_width());
	}
	else
	{
		convert_scanline<R8G8B8>(_desiredFormat, &_unpackedData[_scanLine * get_width() * _channels * _bitDepth / 8], destination, get_width());
	}
	
	_scanLine++;
}

void DecompressorPng::skip_scanline(int count)
{
	_scanLine += count;
}

}
}